﻿using System;
using System.Collections.Generic;
using System.IO;
using Persistencia;
using System.Data;

namespace Indexador
{
    public class FileSystemWatcherManager
    {
        private static FileSystemWatcherManager _fileSystemWatcherMgr;
        private Dictionary<int, FileSystemElementWatcher> _fileSystemWatchersMap;
        private IIndexadorMgr _indexadorMgr;        

        private FileSystemWatcherManager()
        {
            _fileSystemWatchersMap = new Dictionary<int, FileSystemElementWatcher>();
            _indexadorMgr = new IndexadorMgr();
            InicializarFileSysteWatcherManager();
        }

        #region Public Methods

        private void InicializarFileSysteWatcherManager()
        {
            FileSystemElementPersistence fseP = new FileSystemElementPersistence();
            List<FileSystemElement> fseList = fseP.GetFileSystemElementsMonitoreados();

            foreach (FileSystemElement fse in fseList)
            {
                CrearFileSystemWatcher(fse);
            }
        }

        public static FileSystemWatcherManager GetInstance()
        {
            if (_fileSystemWatcherMgr == null)
            {
                _fileSystemWatcherMgr = new FileSystemWatcherManager();
            }

            return _fileSystemWatcherMgr; ;
        }

        public void CrearFileSystemWatcher(FileSystemElement fse)
        {
            FileSystemWatcher fsw = new FileSystemWatcher();
            FileSystemWatcher fswFolder = null;
            bool ok = true;
            if (fse.Is_File)
            {
                if(!File.Exists(fse.Path + @"\" + fse.Nombre + "." + fse.Extension))
                    ok = false;
            }
            else
            {
                if (!Directory.Exists(fse.Path + @"\" + fse.Nombre))
                    ok = false;
            }
            if (ok)
            {
                if (fse.Is_File)
                {
                    fsw.Filter = fse.Nombre + "." + fse.Extension;
                    fsw.Path = fse.Path;
                }
                else
                {
                    fsw.Filter = "*";
                    fsw.Path = fse.Path + @"\" + fse.Nombre;
                }

                fsw.NotifyFilter = NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.DirectoryName |NotifyFilters.LastAccess | NotifyFilters.LastWrite;
                fsw.IncludeSubdirectories = fse.Tiene_Subcarpetas;
                fsw.InternalBufferSize = 26000;

                fsw.Changed += new FileSystemEventHandler(OnChanged);
                fsw.Created += new FileSystemEventHandler(OnChanged);
                fsw.Deleted += new FileSystemEventHandler(OnChanged);
                fsw.Renamed += new RenamedEventHandler(OnRenamed);

                fsw.EnableRaisingEvents = true;

                //En el caso de que se cree un fsw para una carpeta tendre que crear 2 fsw, uno q vigile el contenido de la carpeta y 
                //otro que vigile la carpeta.
                //Debere a lo largo de los metodos vigilar si es o no un file para saber que objeto fsw tengo que usar
                if (!fse.Is_File)
                {
                    fswFolder = new FileSystemWatcher();
                    fswFolder.Filter = fse.Nombre;
                    fswFolder.Path = fse.Path;
                    fswFolder.NotifyFilter = NotifyFilters.Size | NotifyFilters.DirectoryName | NotifyFilters.LastAccess | NotifyFilters.LastWrite;
                    fswFolder.IncludeSubdirectories = true;

                    fswFolder.Deleted += new FileSystemEventHandler(OnChanged);
                    fswFolder.Renamed += new RenamedEventHandler(OnRenamed);

                    fswFolder.EnableRaisingEvents = true;
                }

                FileSystemElementWatcher fsew = new FileSystemElementWatcher(fse.Id, fsw, fswFolder);
                _fileSystemWatchersMap.Add(fse.Id, fsew);
            }
        }

        public void EliminarFileSystemWatcher(int fseId)
        {
            FileSystemElementWatcher fsew = null;
            bool ok = _fileSystemWatchersMap.TryGetValue(fseId, out fsew);
            if (ok)
            {
                fsew.Fsw.EnableRaisingEvents = false;
                if(fsew.FswParentFolder != null)
                    fsew.FswParentFolder.EnableRaisingEvents = false;
            }

            _fileSystemWatchersMap.Remove(fseId);
        }
      
        public void ModificarFileSystemWatcher(int fseId, FileSystemElement fseNuevo)
        {
            FileSystemElementWatcher fsew = null;
            bool ok = _fileSystemWatchersMap.TryGetValue(fseId, out fsew);
            if (ok)
            {
                if (fseNuevo.Is_File)
                {
                    fsew.Fsw.Path = fseNuevo.Path;
                    fsew.Fsw.Filter = fseNuevo.Nombre;
                    fsew.Fsw.IncludeSubdirectories = fseNuevo.Tiene_Subcarpetas;
                    if (fsew.FswParentFolder != null)
                    {
                        fsew.FswParentFolder.EnableRaisingEvents = false;
                        fsew.FswParentFolder = null;
                    }
                }
                else
                {
                    fsew.Fsw.Filter = "*";
                    fsew.Fsw.Path = fseNuevo.Path + @"\" + fseNuevo.Nombre;

                    if (fsew.FswParentFolder == null)
                    {

                        FileSystemWatcher fswFolder = new FileSystemWatcher();
                        fswFolder.Filter = fseNuevo.Nombre;
                        fswFolder.Path = fseNuevo.Path;
                        fswFolder.NotifyFilter = NotifyFilters.Size | NotifyFilters.DirectoryName;
                        fswFolder.IncludeSubdirectories = true;

                        fswFolder.Deleted += new FileSystemEventHandler(OnChanged);
                        fswFolder.Renamed += new RenamedEventHandler(OnRenamed);

                        fswFolder.EnableRaisingEvents = true;
                        fsew.FswParentFolder = fswFolder;
                    }
                    else
                    {
                        fsew.FswParentFolder.Filter = fseNuevo.Nombre;
                        fsew.FswParentFolder.Path = fseNuevo.Path;
                    }
                }
            }
        }

        public int GetFsWatcherFsElementId(String path, String filter)
        {
            int indice = -1;
            foreach (FileSystemElementWatcher fsew in _fileSystemWatchersMap.Values)
            {
                if (fsew.Fsw.Path == path && fsew.Fsw.Filter == filter)
                {
                    indice = fsew.FseId;
                    break;
                }
                else
                {
                    if (fsew.FswParentFolder.Path == path && fsew.FswParentFolder.Filter == filter)
                    {
                        indice = fsew.FseId;
                        break;
                    }
                }
            }

            return indice;
        }

        #endregion

        #region Events Methods

        public static void OnChanged(Object source, FileSystemEventArgs e)
        {
            FileSystemElementPersistence fsePersistence = new FileSystemElementPersistence();
            //Aca se fija si es un arvhivo o una carpeta el elemento modificado
            int fseId = FileSystemWatcherManager.GetInstance().GetFsWatcherFsElementId(((FileSystemWatcher)source).Path, ((FileSystemWatcher)source).Filter);
            switch (e.ChangeType)
            {
                case WatcherChangeTypes.Created:
                    FileSystemElement fsElement = fsePersistence.ObtenerFileSystemElement(fseId);
                    bool isFile = CheckIsFile(e.FullPath);
                    if (isFile)
                    {
                        FileInfo archivo = new FileInfo(e.FullPath);
                        FileSystemElement file = new FileSystemElement();
                        file.Extension = archivo.Extension;
                        file.Nombre = archivo.Name;
                        file.Tamaño = Convert.ToInt32(archivo.Length);
                        file.Path = archivo.DirectoryName;
                        file.Fecha_Creacion = GetFechaCreacion();
                        file.Is_File = true;
                        file.Tiene_Subcarpetas = false;
                        file.Usuario_Creacion = fsElement.Usuario_Creacion;
                        file.Monitoreado = false;
                        FileSystemWatcherManager.GetInstance().IndexarFileSystemElement(file);
                    }
                    else
                    {
                        DirectoryInfo directorio = new DirectoryInfo(e.FullPath);
                        FileSystemElement fseFolder = new FileSystemElement();
                        fseFolder.Extension = "";
                        fseFolder.Nombre = directorio.Name;
                        fseFolder.Tamaño = 0;
                        if (directorio.Parent != null) fseFolder.Path = directorio.Parent.FullName;
                        fseFolder.Fecha_Creacion = GetFechaCreacion();
                        fseFolder.Is_File = false;
                        fseFolder.Tiene_Subcarpetas = true;
                        fseFolder.Usuario_Creacion = fsElement.Usuario_Creacion;
                        fseFolder.Monitoreado = false;
                        FileSystemWatcherManager.GetInstance().IndexarFileSystemElement(fseFolder);
                    }
                    break;

                case WatcherChangeTypes.Deleted:
                    //Elimino el FileSystemElement relaciondo con el indexador y la base
                    FileSystemElement fseAux = fsePersistence.ObtenerFileSystemElementByFullPath(e.FullPath);
                    if(fseAux!=null)
                        FileSystemWatcherManager.GetInstance().EliminarIndexadoFileSystemElement(fseAux.Id);
                    break;

                case WatcherChangeTypes.Changed:
                    ModificarFileSystemElementSize(fseId);
                    break;

                default:
                    break;
            }
        }

        public static void OnRenamed(Object source, RenamedEventArgs e)
        {
            FileSystemElementPersistence fsePersistence = new FileSystemElementPersistence();
            FileSystemElement fseAux = fsePersistence.ObtenerFileSystemElementByFullPath(e.OldFullPath);
            if (fseAux != null)
            {
                bool isFile = CheckIsFile(e.FullPath);
                if (isFile)
                {
                    fseAux.Nombre = Path.GetFileNameWithoutExtension(e.FullPath);
                    fseAux.Path = Path.GetDirectoryName(e.FullPath);
                    fseAux.Extension = Path.GetExtension(e.FullPath);
                    fseAux.Fecha_Ultima_Modificacion = GetFechaCreacion();
                    fseAux.Is_File = true;
                    fseAux.Tiene_Subcarpetas = false;
                }
                else
                {
                    fseAux.Nombre = Path.GetFileName(e.FullPath);
                    fseAux.Path = Path.GetDirectoryName(e.FullPath);
                    fseAux.Extension = "";
                    fseAux.Fecha_Ultima_Modificacion = GetFechaCreacion();
                    fseAux.Is_File = false;
                }

                FileSystemWatcherManager.GetInstance().ModificarIndexadoDeFileSystemElement(fseAux.Id, fseAux);
            }
        }

        #endregion

        #region FileSystemElementsMethods
        //Mismos metodos que en el AppManager

        public void IndexarFileSystemElement(FileSystemElement fsElement)
        {
            FileSystemElementPersistence fsePersistence = new FileSystemElementPersistence();
            fsElement.Monitoreado = true;
            if (fsElement.Is_File)
            {
                String archivo = @fsElement.Nombre + @"." + @fsElement.Extension;
                FileInfo fileInfo = new FileInfo(archivo);
                if (fileInfo.Exists)
                {
                    fsePersistence.InsertarFileSystemElement(fsElement);
                    _indexadorMgr.IndexarElemento(fsElement);
                }
            }
            else
            {
                String pathCarpeta = @fsElement.Path + @"\" + @fsElement.Nombre;
                DirectoryInfo directorioPrincipal = new DirectoryInfo(pathCarpeta);
                if (directorioPrincipal.Exists)
                {
                    fsePersistence.InsertarFileSystemElement(fsElement);
                    _indexadorMgr.IndexarElemento(fsElement);
                    //recorro los archivos
                    FileInfo[] archivos = directorioPrincipal.GetFiles();
                    foreach (var archivo in archivos)
                    {
                        FileSystemElement file = new FileSystemElement();
                        file.Extension = archivo.Extension;
                        file.Nombre = archivo.Name;
                        file.Tamaño = Convert.ToInt32(archivo.Length);
                        file.Path = archivo.DirectoryName;
                        file.Fecha_Creacion = GetFechaCreacion();
                        file.Is_File = true;
                        file.Tiene_Subcarpetas = false;
                        file.Usuario_Creacion = fsElement.Usuario_Creacion;
                        file.Monitoreado = false;
                        fsePersistence.InsertarFileSystemElement(file);
                        _indexadorMgr.IndexarElemento(file);
                    }
                    //recorro los directorios
                    if (fsElement.Tiene_Subcarpetas)
                    {
                        DirectoryInfo[] directorios = directorioPrincipal.GetDirectories();
                        foreach (var directorio in directorios)
                        {
                            FileSystemElement fseFolder = new FileSystemElement();
                            fseFolder.Extension = "";
                            fseFolder.Nombre = directorio.Name;
                            fseFolder.Tamaño = 0;
                            if (directorio.Parent != null) fseFolder.Path = directorio.Parent.FullName;
                            fseFolder.Fecha_Creacion = GetFechaCreacion();
                            fseFolder.Is_File = false;
                            fseFolder.Tiene_Subcarpetas = true;
                            fseFolder.Usuario_Creacion = fsElement.Usuario_Creacion;
                            fseFolder.Monitoreado = false;
                            IndexarFileSystemElement(fseFolder);
                        }
                    }
                }
            }
        }

        private void EliminarIndexadoFileSystemElement(int idFsElement)
        {
            FileSystemElementPersistence fsePersistence = new FileSystemElementPersistence();
            FileSystemElement fsElement = fsePersistence.ObtenerFileSystemElement(idFsElement);
            bool monitoreado = fsElement.Monitoreado;
            if (fsElement.Is_File)
            {
                fsePersistence.EliminarFileSystemElement(idFsElement);
                _indexadorMgr.EliminarElemento(fsElement);
            }
            else
            {
                fsePersistence.EliminarFileSystemElement(idFsElement);
                String pathCarpeta = @fsElement.Path + @"\" + @fsElement.Nombre;
                List<FileSystemElement> elementosAnidados = fsePersistence.BuscarFSE(pathCarpeta);
                foreach (FileSystemElement fileSystemElement in elementosAnidados)
                {
                    fsePersistence.EliminarFileSystemElement(fileSystemElement.Id);
                    _indexadorMgr.EliminarElemento(fileSystemElement);
                }
            }
            if (monitoreado)
            {
                EliminarFileSystemWatcher(idFsElement);
            }
        }

        private void ModificarIndexadoDeFileSystemElement(int idFileSystemElementOriginal, FileSystemElement fsElement)
        {
            FileSystemElementPersistence fsePersistence = new FileSystemElementPersistence();
            FileSystemElement original = fsePersistence.ObtenerFileSystemElement(idFileSystemElementOriginal);
            fsElement.Monitoreado = original.Monitoreado;
            if (fsElement.Is_File)
            {
                fsePersistence.ModificarFileSystemElement(idFileSystemElementOriginal, fsElement);
                _indexadorMgr.ModificarElemento(original, fsElement);
                if (original.Monitoreado)
                {
                    ModificarFileSystemWatcher(idFileSystemElementOriginal, fsElement);
                }
            }
            else
            {
                String pathCarpeta = @fsElement.Path + @"\" + @fsElement.Nombre;
                DirectoryInfo directorioPrincipal = new DirectoryInfo(pathCarpeta);
                if (directorioPrincipal.Exists)
                {
                    pathCarpeta = original.Path + @"\" + original.Nombre;
                    fsePersistence.EliminarFileSystemElement(idFileSystemElementOriginal);
                    if (original.Monitoreado)
                    {
                        EliminarFileSystemWatcher(original.Id);
                    }
                    List<FileSystemElement> elementosAnidados = fsePersistence.BuscarFSE(pathCarpeta);
                    foreach (FileSystemElement fileSystemElement in elementosAnidados)
                    {
                        fsePersistence.EliminarFileSystemElement(fileSystemElement.Id);
                        _indexadorMgr.EliminarElemento(fileSystemElement);
                    }
                    FileSystemElement fse = new FileSystemElement();

                    fse.Extension = fsElement.Extension;
                    fse.Nombre = fsElement.Nombre;
                    fse.Is_File = fsElement.Is_File;
                    fse.Tiene_Subcarpetas = false;
                    fse.Tamaño = fsElement.Tamaño;
                    fse.Path = fsElement.Path;
                    fse.Fecha_Creacion = fsElement.Fecha_Creacion;
                    fse.Usuario_Creacion = fsElement.Usuario_Creacion;
                    fse.Monitoreado = fsElement.Monitoreado;
                    IndexarFileSystemElement(fse);
                }
            }
        }


        /// <summary>
        /// Obtiene la fecha del sistema.
        /// </summary>
        /// <returns></returns>
        private static DateTime GetFechaCreacion()
        {
            TimeMgr.TimeMgr mgr = new TimeMgr.TimeMgr();
            return mgr.ObtenerFecha();
        }

        #endregion

        #region Private Methods

        private static bool CheckIsFile(string path)
        {
            bool isFile = !((File.GetAttributes(path) & FileAttributes.Directory)
                 == FileAttributes.Directory);
            return isFile;
        }

        private static bool IsFileSystemWatcher(string fullpath, FileSystemWatcher fileSystemWatcher)
        {
            bool isFSW = false;
            bool isFolder = fileSystemWatcher.Filter.Equals("*");
            if (isFolder && fileSystemWatcher.Path.Equals(fullpath))
                isFSW = true;
            else if (Path.GetDirectoryName(fullpath).Equals(fileSystemWatcher.Path) && Path.GetFileName(fullpath).Equals(fileSystemWatcher.Filter))
                isFSW = true;

            return isFSW;
        }

        private static void ModificarFileSystemElementSize(int fseId)
        {
            FileSystemElementPersistence fseP = new FileSystemElementPersistence();
            FileSystemElement fse = fseP.ObtenerFileSystemElement(fseId);
            long tamano = 0;
            string pathTotal = fse.Path + @"\" + fse.Nombre;

            if (fse.Is_File)
            {
                pathTotal += "." + fse.Extension;
                FileInfo f = new FileInfo(pathTotal);
                tamano = f.Length;
            }
            else
            {
                tamano = GetDirectorySize(pathTotal);
            }

            fseP.ModificarFileSystemElementTamano(fse.Id, (int)tamano);
        }

        private static long GetDirectorySize(string rootdir)
        {
            try
            {
                long dirSize = 0;
                DirectoryInfo[] DI = new DirectoryInfo(rootdir).GetDirectories("*.*", SearchOption.AllDirectories);
                FileInfo[] FI = new DirectoryInfo(rootdir).GetFiles("*.*", SearchOption.AllDirectories);
                foreach (FileInfo file in FI)
                {
                    dirSize += file.Length;
                }

                return dirSize;
            }
            catch (DirectoryNotFoundException)
            {   
            }
            catch (Exception)
            {
            }

            return 0;
        }

        #endregion

    }
}
